'use strict'; const { Document } = require('/document.js'); const { DocumentCommand } = require('/commands.js'); const { Dialog, DialogResult, UnitType } = require('/dialog.js'); const { Bitmap, RasterFormat } = require('/rasterobject.js'); const { createPixelReader, createPixelReaderWriter } = require('/pixelaccessor.js'); const { Selection } = require('/selections.js'); function isOk(result) { return (result?.value ?? result) === DialogResult.Ok.value; } function processNode(doc, node, offsetX, offsetY) { const W = node.rasterWidth; const H = node.rasterHeight; console.log(`Processing raster node ${W}×${H}`); // Read source pixels into a buffer const srcBuf = node.createCompatibleBuffer(true); const srcArr = new Uint8Array(srcBuf.buffer); // Create output bitmap and buffer const dstBitmap = Bitmap.create(W, H, RasterFormat.RGBA8); const dstBuf = dstBitmap.createCompatibleBuffer(false); const dstArr = new Uint8Array(dstBuf.buffer); const stride = W * 4; // RGBA8 = 4 bytes per pixel for (let y = 0; y < H; y++) { for (let x = 0; x < W; x++) { const base = y * stride + x * 4; // Current pixel (r, g, b, a) const r0 = srcArr[base]; const g0 = srcArr[base + 1]; const b0 = srcArr[base + 2]; const a0 = srcArr[base + 3]; // Neighbour pixel at (x + offsetX, y + offsetY) — clamped to bounds const nx = Math.min(W - 1, Math.max(0, x + offsetX)); const ny = Math.min(H - 1, Math.max(0, y + offsetY)); const nBase = ny * stride + nx * 4; const r1 = srcArr[nBase]; const g1 = srcArr[nBase + 1]; const b1 = srcArr[nBase + 2]; const a1 = srcArr[nBase + 3]; // Average the two pixels dstArr[base] = Math.round((r0 + r1) / 2); dstArr[base + 1] = Math.round((g0 + g1) / 2); dstArr[base + 2] = Math.round((b0 + b1) / 2); dstArr[base + 3] = Math.round((a0 + a1) / 2); } } // Copy modified buffer into the output bitmap and write back dstBuf.copyTo(dstBitmap, { x: 0, y: 0, width: W, height: H }, 0, 0); doc.executeCommand(DocumentCommand.createReplaceBitmap(Selection.create(doc, node), dstBitmap)); console.log(`Averaged with neighbour at offset (${offsetX}, ${offsetY})`); } // ---- MAIN ---- const doc = Document.current; if (!doc) { console.log('ERROR: No document open.'); } else { const selectedNodes = doc.selection.nodes.filter(n => n.isRasterNode).toArray(); if (selectedNodes.length === 0) { console.log('Please select one or more pixel layers first.'); } else { const dlg = Dialog.create('Pixel Neighbour Average / Ghost (RGB)'); const col = dlg.addColumn(); const g1 = col.addGroup(''); const oxCtrl = g1.addUnitValueEditor('Offset X (pixels)', UnitType.Number, UnitType.Number, 200, -1000, 1000).setPrecision(0); const oyCtrl = g1.addUnitValueEditor('Offset Y (pixels)', UnitType.Number, UnitType.Number, 0, -1000, 1000).setPrecision(0); dlg.initialWidth = 380; const result = dlg.runModal(); if (isOk(result)) { const offsetX = Math.round(oxCtrl.value); const offsetY = Math.round(oyCtrl.value); const spread = doc.currentSpread; doc.executeCommand(DocumentCommand.createSetCurrentSpread(spread)); for (const node of selectedNodes) { processNode(doc, node, offsetX, offsetY); } console.log(`Done! Processed ${selectedNodes.length} layer(s).`); } } }